﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

namespace Ingenious.Graph
{
    #region Supporting Enums
    public enum ItemType
    {
        // Not delay-loaded
        Value,
        // Delay-loaded
        ValueList,
        // Delay-loaded
        ReferenceList,
        // Delay-loaded
        OtherReference
    }
    #endregion

    #region Supporting Classes
    public class ItemWrappingInfo
    {
        public ItemType ItemType { get; set; }
        public Type GenericCollectionType { get; set; }
    }
    #endregion

    public class TypeHelper
    {
        private static readonly string CommonLanguageRuntimeLibraryScopeName = "CommonLanguageRuntimeLibrary";

        public static bool IsValueType(object obj)
        {
            return obj.GetType().Module.ScopeName == CommonLanguageRuntimeLibraryScopeName;
        }

        public static bool IsValueItemType(ItemType itemType)
        {
            return itemType == ItemType.Value;
        }

        public static bool IsReferenceItemType(ItemType itemType)
        {
            return itemType == ItemType.ValueList || itemType == ItemType.ReferenceList || itemType == ItemType.OtherReference;
        }

        public static ItemWrappingInfo GetItemWrappingInfo(Type itemType)
        {
            ItemWrappingInfo itemWrappingInfo = new ItemWrappingInfo();
            itemWrappingInfo.ItemType = ItemType.Value;

            if (itemType.Module.ScopeName == CommonLanguageRuntimeLibraryScopeName)
            {
                // If it is a value type, we simply set this directly as it is
                // not delay loaded
            }
            else
            {
                Type genericCollectionType = itemType.GetInterfaces().
                    Where(t => t.IsGenericType &&
                    typeof(ICollection<>).IsAssignableFrom(
                    t.GetGenericTypeDefinition()))
                    .FirstOrDefault();

                if (genericCollectionType != null)
                {
                    if (genericCollectionType.IsValueType)
                    {
                        // List of value types
                        itemWrappingInfo.ItemType = ItemType.ValueList;
                    }
                    else
                    {
                        // List of references (wrapped by entity proxies)
                        itemWrappingInfo.ItemType = ItemType.ReferenceList;
                    }

                    itemWrappingInfo.GenericCollectionType = genericCollectionType;
                }
                else
                {
                    // It's not a collection, so it must be another reference type
                    itemWrappingInfo.ItemType = ItemType.OtherReference;
                }
            }

            return itemWrappingInfo;
        }

        // Note: This is a performance-critical method
        public static object WrapItem<TTripleId>(object item, INode<TTripleId> parent, ItemWrappingInfo itemWrappingInfo, Uri objectUri = null)
        {
            object wrappedItem = null;

            switch (itemWrappingInfo.ItemType)
            {
            case ItemType.Value:
                {
                    wrappedItem = item;
                }
                break;
            //case ItemType.ValueList:
            //    {
            //        // Create the reflected type for the same generic value parameter constraints
            //        Type genericEntityList = typeof(NodeValueList<,>).GetGenericTypeDefinition().MakeGenericType(new Type[] { itemWrappingInfo.GenericCollectionType.GetGenericParameterConstraints()[0] });
            //        ConstructorInfo ci = genericEntityList.GetConstructor(new Type[] { typeof(INode<>), itemWrappingInfo.GenericCollectionType });

            //        wrappedItem = ci.Invoke(new object[] { parent, item });
            //    }
            //    break;
            //case ItemType.ReferenceList:
            //    {
            //        // Create the reflected type for the same generic reference parameter constraints
            //        Type genericEntityList = typeof(NodeReferenceList<>).GetGenericTypeDefinition().MakeGenericType(new Type[] { itemWrappingInfo.GenericCollectionType.GetGenericParameterConstraints()[0] });
            //        ConstructorInfo ci = genericEntityList.GetConstructor(new Type[] { typeof(INode<>), itemWrappingInfo.GenericCollectionType });

            //        wrappedItem = ci.Invoke(new object[] { parent, item });
            //    }
            //    break;
            case ItemType.OtherReference:
                {
                    if (objectUri == null)
                    {
                        throw new InvalidOperationException("Require an object URI when creating a reference node");
                    }

                    wrappedItem = new Node<TTripleId>(objectUri, parent.Graph, item);
                }
                break;
            }

            return wrappedItem;
        }
    }
}
